/*
 * Copyright (C) 2011, EADS France
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 */
package xowl.gmi.view.diagram;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 *
 * @author Laurent WOUTERS
 */
public class Container extends Widget {
    protected List<Widget> children;
    protected GMIAlignment alignment;
    protected boolean isPacked;
    
    public Container(GMIAlignment alignment, boolean isPacked) {
        this.children = new ArrayList<>();
        this.alignment = alignment;
        this.isPacked = isPacked;
    }
    
    public void addChild(Widget child) { children.add(child); }
    public void addChild(Widget child, int index) { children.add(index, child); }
    public void removeChild(Widget child) { children.remove(child); }
    
    protected boolean isAlignmentStack() {
        return (alignment == GMIAlignment.StackCenter ||
                alignment == GMIAlignment.StackLeft ||
                alignment == GMIAlignment.StackRight ||
                alignment == GMIAlignment.StackTop ||
                alignment == GMIAlignment.StackTopLeft ||
                alignment == GMIAlignment.StackTopRight ||
                alignment == GMIAlignment.StackBottom ||
                alignment == GMIAlignment.StackBottomLeft ||
                alignment == GMIAlignment.StackBottomRight);
    }
    protected boolean isAlignmentHorizontal() {
        return (alignment == GMIAlignment.Top ||
                alignment == GMIAlignment.Bottom ||
                alignment == GMIAlignment.Horizontal);
    }
    protected boolean isAlignmentVertical() {
        return (alignment == GMIAlignment.Left ||
                alignment == GMIAlignment.Right ||
                alignment == GMIAlignment.Vertical);
    }
    
    @Override protected void buildStackChildren(List<Widget> stack, Couple input) {
        if (isAlignmentStack())
            for (Widget child : children)
                child.buildStackAt(stack, input);
        else
            for (Widget child : children)
                if (child.buildStackAt(stack, input))
                    return;
    }
    
    @Override public Couple getMinSize() {
        List<Couple> mins = new ArrayList<>();
        for (Widget child : children)
            mins.add(child.getMinSize());
        return getMinSizeFor(mins);
    }
    @Override public Couple getMaxSize() {
        if (isPacked) return getMinSize();
        return MAXSIZE;
    }
    @Override public Couple layout() {
        List<Couple> dims = new ArrayList<>();
        for (Widget child : children)
            dims.add(child.getMinSize());
        dimension = getMinSizeFor(dims);
        if (isAlignmentStack())
            layoutMinCenter();
        else if (isAlignmentHorizontal())
            layoutMinHorizontal(dims);
        else
            layoutMinVertical(dims);
        return dimension;
    }
    @Override public Couple layout(Couple bounds) {
        if (isPacked)
            return layout();
        dimension = getMinSize();
        if (bounds != null)
            dimension = new Couple(Math.max(bounds.x, dimension.x), Math.max(bounds.y, dimension.y));
        if (isAlignmentStack())
            layoutMaxCenter();
        else if (isAlignmentHorizontal())
            layoutMaxHorizontal();
        else
            layoutMaxVertical();
        return dimension;
    }
    @Override public void paint(java.awt.Graphics2D g2d, float alpha, DiagramStyle style, Couple offset) {
        for (int i=children.size()-1; i!=-1; i--)
            children.get(i).paint(g2d, alpha * transparency, style, position.plus(offset));
    }
    @Override protected void exportSVG(org.w3c.dom.Document doc, org.w3c.dom.Element parent, float alpha, DiagramStyle style, Couple offset) {
        for (int i=children.size()-1; i!=-1; i--)
            children.get(i).exportSVG(doc, parent, alpha * transparency, style, position.plus(offset));
    }
    
    protected Couple getMinSizeFor(List<Couple> children) {
        if (isAlignmentStack()) {
            double maxWidth = 0;
            double maxHeight = 0;
            for (Couple child : children) {
                if (child.x > maxWidth)
                    maxWidth = child.x;
                if (child.y > maxHeight)
                    maxHeight = child.y;
            }
            return new Couple(maxWidth, maxHeight);
        } else if (isAlignmentHorizontal()) {
            double width = 0;
            double maxHeight = 0;
            for (Couple child : children) {
                width += child.x;
                if (child.y > maxHeight)
                    maxHeight = child.y;
            }
            return new Couple(width, maxHeight);
        } else {
            double height = 0;
            double maxWidth = 0;
            for (Couple child : children) {
                height += child.y;
                if (child.x > maxWidth)
                    maxWidth = child.x;
            }
            return new Couple(maxWidth, height);
        }
    }
    
    protected Couple getStackPosition(Couple dim) {
        double offsetX = 0;
        double offsetY = 0;
        switch (alignment) {
            case StackLeft:
                offsetY = (dimension.y - dim.y) / 2;
                break;
            case StackRight:
                offsetX = (dimension.x - dim.x);
                offsetY = (dimension.y - dim.y) / 2;
                break;
            case StackTop:
                offsetX = (dimension.x - dim.x) / 2;
                break;
            case StackBottom:
                offsetX = (dimension.x - dim.x) / 2;
                offsetY = (dimension.y - dim.y);
                break;
            case StackTopRight:
                offsetX = (dimension.x - dim.x);
                break;
            case StackBottomLeft:
                offsetY = (dimension.y - dim.y);
                break;
            case StackBottomRight:
                offsetX = (dimension.x - dim.x);
                offsetY = (dimension.y - dim.y);
                break;
            default:
                offsetX = (dimension.x - dim.x) / 2;
                offsetY = (dimension.y - dim.y) / 2;
                break;
        }
        return new Couple(offsetX, offsetY);
    }
    
    protected void layoutMinCenter() {
        for (int i=0; i!=children.size(); i++) {
            Couple dim = null;
            if (isPacked) dim = children.get(i).layout();
            else dim = children.get(i).layout(dimension);
            children.get(i).setPosition(getStackPosition(dim));
        }
    }
    protected void layoutMinVertical(List<Couple> dims) {
        double currentY = 0;
        for (int i=0; i!=children.size(); i++) {
            Couple dim = dims.get(i);
            if (isPacked)
                dim = children.get(i).layout();
            else {
                double maxWidth = Math.max(dimension.x, dim.x);
                dim = children.get(i).layout(new Couple(maxWidth, dim.y));
            }
            double offsetX = 0;
            if (alignment == GMIAlignment.Vertical)
                offsetX = (dimension.x - dim.x) / 2;
            else if (alignment == GMIAlignment.Right)
                offsetX = dimension.y - dim.y;
            children.get(i).setPosition(new Couple(offsetX, currentY));
            currentY += dim.y;
        }
    }
    protected void layoutMinHorizontal(List<Couple> dims) {
        double currentX = 0;
        for (int i=0; i!=children.size(); i++) {
            Couple dim = dims.get(i);
            if (isPacked)
                dim = children.get(i).layout();
            else {
                double maxHeight = Math.max(dimension.y, dim.y);
                dim = children.get(i).layout(new Couple(dim.x, maxHeight));
            }
            double offsetY = 0;
            if (alignment == GMIAlignment.Horizontal)
                offsetY = (dimension.y - dim.y) / 2;
            else if (alignment == GMIAlignment.Bottom)
                offsetY = dimension.y - dim.y;
            children.get(i).setPosition(new Couple(currentX, offsetY));
            currentX += dim.x;
        }
    }
    
    protected void layoutMaxCenter() {
        for (Widget child : children) {
            Couple dim = child.layout(dimension);
            child.setPosition(getStackPosition(dim));
        }
    }
    protected void layoutMaxVertical() {
        List<Couple> maxes = new ArrayList<>();
        double fixedHeight = 0;
        int varCount = 0;
        for (Widget child : children) {
            Couple dim = child.getMaxSize();
            maxes.add(dim);
            if (dim == MAXSIZE)
                varCount++;
            else
                fixedHeight += dim.y;
        }
        Couple varDim = null;
        double currentY = 0;
        if (varCount != 0)
            varDim = new Couple(dimension.x, (dimension.y - fixedHeight) / varCount);
        else
            currentY = (dimension.y - fixedHeight) / 2;
        for (int i=0; i!=children.size(); i++) {
            Couple max = maxes.get(i);
            Couple dim = null;
            if (max == MAXSIZE)
                dim = children.get(i).layout(varDim);
            else
                dim = children.get(i).layout(max);
            double offsetX = 0;
            if (alignment == GMIAlignment.Vertical)
                offsetX = (dimension.x - dim.x) / 2;
            else if (alignment == GMIAlignment.Right)
                offsetX = dimension.x - dim.x;
            children.get(i).setPosition(new Couple(offsetX, currentY));
            currentY += dim.y;
        }
    }
    protected void layoutMaxHorizontal() {
        List<Couple> maxes = new ArrayList<>();
        double fixedWidth = 0;
        int varCount = 0;
        for (Widget child : children) {
            Couple dim = child.getMaxSize();
            maxes.add(dim);
            if (dim == MAXSIZE)
                varCount++;
            else
                fixedWidth += dim.x;
        }
        Couple varDim = null;
        double currentX = 0;
        if (varCount != 0)
            varDim = new Couple((dimension.x - fixedWidth) / varCount, dimension.y);
        else
            currentX = (dimension.x - fixedWidth) / 2;
        for (int i=0; i!=children.size(); i++) {
            Couple max = maxes.get(i);
            Couple dim = null;
            if (max == MAXSIZE)
                dim = children.get(i).layout(varDim);
            else
                dim = children.get(i).layout(max);
            double offsetY = 0;
            if (alignment == GMIAlignment.Horizontal)
                offsetY = (dimension.y - dim.y) / 2;
            else if (alignment == GMIAlignment.Bottom)
                offsetY = dimension.y - dim.y;
            children.get(i).setPosition(new Couple(currentX, offsetY));
            currentX += dim.x;
        }
    }
    
    public int getInsertionIndex(Couple location) {
        if (isAlignmentStack())
            return -1;
        if (isAlignmentHorizontal()) {
            for (int i=0; i!=children.size(); i++) {
                Widget child = children.get(i);
                if (location.x < child.getPosition().x + child.getSize().x / 2)
                    return i;
            }
            return children.size();
        } else {
            for (int i=0; i!=children.size(); i++) {
                Widget child = children.get(i);
                if (location.y < child.getPosition().y + child.getSize().y / 2)
                    return i;
            }
            return children.size();
        }
    }
    
    public double getInsertionOffset(Couple location) {
        if (isAlignmentStack())
            return -1;
        if (isAlignmentHorizontal()) {
            if (children.isEmpty())
                return 0;
            for (int i=0; i!=children.size(); i++) {
                Widget child = children.get(i);
                if (location.x < child.getPosition().x + child.getSize().x / 2)
                    return child.getPosition().x;
            }
            Widget last = children.get(children.size()-1);
            return last.getPosition().x + last.getSize().x;
        } else {
            if (children.isEmpty())
                return 0;
            for (int i=0; i!=children.size(); i++) {
                Widget child = children.get(i);
                if (location.y < child.getPosition().y + child.getSize().y / 2)
                    return child.getPosition().y;
            }
            Widget last = children.get(children.size()-1);
            return last.getPosition().y + last.getSize().y;
        }
    }
    
    @Override public List<Couple> getVertices() {
        List<Couple> set = new ArrayList<>();
        for (Widget child : children) {
            List<Couple> temp = child.getVertices();
            for (Couple p : temp)
                set.add(p.plus(child.position));
        }
        return getHull(set);
    }
    
    private List<Couple> getHull(List<Couple> p) {
        int n = p.size();
        int k = 0;
        Collections.sort(p, getComparator());
        Couple[] h = new Couple[n*2];
        for (int i=0; i!=n; i++) {
            while (k >= 2 && cross(h[k-2], h[k-1], p.get(i)) <= 0) k--;
            h[k++] = p.get(i);
        }
        for (int i = n-2, t = k+1; i >= 0; i--) {
            while (k >= t && cross(h[k-2], h[k-1], p.get(i)) <= 0) k--;
            h[k++] = p.get(i);
        }
        List<Couple> result = new ArrayList<>();
        for (int i=0; i!=k-1; i++)
            result.add(h[i]);
        return result;
    }
    
    private double cross(Couple p1, Couple p2, Couple p3) {
        return (p2.x - p1.x)*(p3.y - p1.y) - (p2.y - p1.y)*(p3.x - p1.x);
    }
    
    private Comparator<Couple> getComparator() {
        return new Comparator<Couple>() {
            @Override public int compare(Couple p1, Couple p2) {
                int c = Double.compare(p1.x, p2.x);
                if (c != 0) return c;
                return Double.compare(p1.y, p2.y);
            }
        };
    }
}
